package org.nulldevice.tankshot.ai;

import java.awt.Point;
import java.awt.Rectangle;
import java.util.ArrayList;

import org.nulldevice.tankshot.GameContainer;
import org.nulldevice.tankshot.entities.Tank;
import org.nulldevice.tankshot.entities.Wall;

public abstract class AI {
	protected ArrayList<Tank> enemyTanks;
	protected ArrayList<Target> targets;
	protected double[] distanceToWalls = new double[360];
	protected static double radius = 200;
	protected double speedFactor = 1; // always drive forward for now

	Tank owner;

	public void setOwner(Tank owner) {
		this.owner = owner;

	}

	public void setEnemyTanks(ArrayList<Tank> enemyTanks) {
		this.enemyTanks = enemyTanks;
	}

	abstract public void init();

	abstract public void prepareMove();

	protected void getTargets() {
		ArrayList<Target> newTargets = new ArrayList<Target>();

		if (enemyTanks != null) {
			for (Tank enemy : enemyTanks) {
				int enemyX = enemy.getX(), enemyY = enemy.getY();

				checkPossibleTargets(newTargets, enemy, enemyX, enemyY);

			}
		}
		this.targets = newTargets;
		if (targets.size() > 0) {
			owner.setAimingPoint(targets.get(0).getX(), targets.get(0).getY());
		}
	}

	private void normalizeAngles() {
		owner.setDrivingAngle((owner.getDrivingAngle() + 360) % 360);
		owner
				.setNeededDrivingAngle((owner.getNeededDrivingAngle() + 360) % 360);
	}

	private double checkDistanceToWall(int i, double x1, double y1, double x2,
			double y2, double x, double y) {
		Point p = this.getIntersectionPoint(x1, y1, x2, y2, owner.getX(), owner
				.getY(), x, y);
		double distance;
		if (p == null)
			distance = radius;
		else
			distance = Math.sqrt((p.getX() - owner.getX())
					* (p.getX() - owner.getX()) + (p.getY() - owner.getY())
					* (p.getY() - owner.getY()));

		return distance;
	}

	private void checkPossibleTargets(ArrayList<Target> newTargets, Tank enemy,
			int enemyX, int enemyY) {
		// check direct aiming
		Target target = new Target(enemy, enemyX, enemyY, new ArrayList<Wall>());
		if (!isTargetBlocked(enemy, target)) {
			newTargets.add(target);
		}

		if (GameContainer.getInstance().getWalls() != null) { // check for
			// indirect
			// aiming

			// get walls which are suitable to bounce a shot
			for (Wall wall : GameContainer.getInstance().getWalls()) {
				Rectangle border = wall.getBorder().getBounds();

				if (border.getMaxX() < enemy.getX()
						&& border.getMaxX() < owner.getX()) {
					// wall is on the left
					int newEnemyX = (int) (2d * border.getMaxX()) - enemyX;
					target = getBouncedTarget(newEnemyX, enemyY, wall, enemy,
							border.getMaxX(), border.getMinY(), border
									.getMaxX(), border.getMaxY());
					if (target != null)
						newTargets.add(target);
				}

				if (border.getMinX() > enemy.getX()
						&& border.getMinX() > owner.getX()) {
					// wall is on the right
					int newEnemyX = (int) (2d * border.getMinX()) - enemyX;
					target = getBouncedTarget(newEnemyX, enemyY, wall, enemy,
							border.getMinX(), border.getMinY(), border
									.getMinX(), border.getMaxY());
					if (target != null)
						newTargets.add(target);
				}
				if (border.getMaxY() < enemy.getY()
						&& border.getMaxY() < owner.getY()) {
					// wall is above
					int newEnemyY = (int) (2d * border.getMaxY()) - enemyY;
					target = getBouncedTarget(enemyX, newEnemyY, wall, enemy,
							border.getMinX(), border.getMaxY(), border
									.getMaxX(), border.getMaxY());
					if (target != null)
						newTargets.add(target);
				}
				if (border.getMinY() > enemy.getY()
						&& border.getMinY() > owner.getY()) {
					// wall is below
					int newEnemyY = (int) (2d * border.getMinY()) - enemyY;
					target = getBouncedTarget(enemyX, newEnemyY, wall, enemy,
							border.getMinX(), border.getMinY(), border
									.getMaxX(), border.getMinY());
					if (target != null)
						newTargets.add(target);
				}
			}

		}
	}

	private Target getBouncedTarget(int targetX, int targetY, Wall wall,
			Tank enemy, double edgeX1, double edgeY1, double edgeX2,
			double edgeY2) {

		Point p = getIntersectionPoint(owner.getX(), owner.getY(), targetX,
				targetY, edgeX1, edgeY1, edgeX2, edgeY2);
		// if(Line2D.linesIntersect(posX, posY, enemyX, newEnemyY,
		// border.getMinX(), border.getMinY(), border.getMaxX(),
		// border.getMinY())) {
		if (p != null) {
			ArrayList<Wall> involvedWalls = new ArrayList<Wall>();
			involvedWalls.add(wall);
			Target target = new Target(enemy, p.x, p.y, involvedWalls);
			if (!isTargetBlocked(enemy, target)) {
				return target;
			}
		}
		return null;
	}

	private Point getIntersectionPoint(double x1, double y1, double x2,
			double y2, double x3, double y3, double x4, double y4) {
		double x, y, m, b, m1, m2, b1, b2;

		if (x1 == x2) { // check for vertical lines
			if (x3 == x4)
				return null; // both vertical lines, either no or infinite
			// intersections
			x = x1;
			m = (y4 - y3) / (x4 - x3);
			b = y3 - x3 * m;
		} else if (x3 == x4) { // check for vertical lines
			x = x3;
			m = (y2 - y1) / (x2 - x1);
			b = y1 - x1 * m;
		} else {
			m1 = (y2 - y1) / (x2 - x1);
			m2 = (y4 - y3) / (x4 - x3);
			if (m1 == m2)
				return null;
			b1 = y1 - x1 * m1;
			b2 = y3 - x3 * m2;
			x = (b2 - b1) / (m1 - m2);
			b = b1;
			m = m1;
		}

		y = m * x + b;

		// check if intersection is really between each points
		if (x < Math.min(x1, x2) || x > Math.max(x1, x2))
			return null;
		if (x < Math.min(x3, x4) || x > Math.max(x3, x4))
			return null;
		if (y < Math.min(y1, y2) || y > Math.max(y1, y2))
			return null;
		if (y < Math.min(y3, y4) || y > Math.max(y3, y4))
			return null;
		return new Point((int) x, (int) y);
	}

	private boolean isTargetBlocked(Tank enemy, Target target) {
		ArrayList<Wall> walls = GameContainer.getInstance().getWalls();
		if (walls != null) {
			for (int i = 0; i < walls.size(); i++) {
				Wall wall = walls.get(i);
				if (!target.isUsingWall(wall)) {
					if (wall.getBorder().getBounds().intersectsLine(
							owner.getX(), owner.getY(), target.getX(),
							target.getY())) {
						return true;
					}
					if (wall.getBorder().getBounds().intersectsLine(
							enemy.getX(), enemy.getY(), target.getX(),
							target.getY())) {
						return true;
					}
				}
			}
		}
		return false;
	}

	protected class Target {
		Tank enemy;
		int targetX, targetY;
		ArrayList<Wall> involvedWalls;

		public Target(Tank enemy, int targetX, int targetY,
				ArrayList<Wall> involvedWalls) {
			this.enemy = enemy;
			this.targetX = targetX;
			this.targetY = targetY;
			this.involvedWalls = involvedWalls;
		}

		public boolean isUsingWall(Wall wall) {
			return involvedWalls.contains(wall);
		}

		public int getX() {
			return targetX;
		}

		public int getY() {
			return targetY;
		}

		public Tank getEnemy() {
			return enemy;
		}
	}

}
